﻿using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TI_BLE_HCI_ClientLib.Controllers;
using TI_BLE_HCI_ClientLib.Controllers.EnhancedControllers;
using TI_BLE_HCI_ClientLib.Controllers.GATT_Entities;
using TI_BLE_HCI_ClientLib.DataEntities;
using TI_BLE_HCI_ClientLib.DataEntities.Advertising;
using TI_BLE_HCI_ClientLib.DataEntities.Advertising.ManufacturerSpecificDataEntities;
using TI_BLE_HCI_ClientLib.HCICommands;

namespace TI_BLE_HCI_ClientLib.Tests
{
    [TestFixture]
    public class HCISimpleSerialPeripheralProfileControllerTester
    {
        [Test]
        public void TestInit()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");
        }

        // new byte[] { 0x2, 0x1, 0x6 }

        [Test]
        public void TestUpdateAdvertisingData()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Init(), "Init() Failed");
            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, new byte[] { 0x2, 0x1, 0x6 }));

            controller.Stop();
        }

        [Test]
        public void TestMakeDiscoverable()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Init(), "Init() Failed");
            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, new byte[] { 0x2, 0x1, 0x6 }));
            Assert.IsTrue(controller.MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny));

            controller.Stop();
        }

        [Test]
        public void TestWaitForLinkEstablishedEvent()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            AutoResetEvent are = new AutoResetEvent(false);

            controller.LinkEstablished += ((evt) =>
                {
                    Trace.TraceInformation("Link Established by {0}", evt.DevAddr.ToHexStringReversed());
                    are.Set();
                });

            Assert.IsTrue(controller.Init(), "Init() Failed");
            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, new byte[] { 0x2, 0x1, 0x6 }));
            Assert.IsTrue(controller.MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny));

            Assert.IsTrue(are.WaitOne(10000));

            controller.Stop();
        }

        [Test]
        public void TestWaitForLinkTerminatedEvent()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            AutoResetEvent are = new AutoResetEvent(false);

            controller.LinkTerminated += ((evt) =>
            {
                Trace.TraceInformation("Link Terminated with reason : {0}", evt.Reason.ToString());
                are.Set();
            });

            Assert.IsTrue(controller.Init(), "Init() Failed");
            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, new byte[] { 0x2, 0x1, 0x6 }));
            Assert.IsTrue(controller.MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny));

            Assert.IsTrue(are.WaitOne(15000));

            controller.Stop();
        }

        [Test]
        public void TestMakeDiscoverableWithSpecificAdvertisingData()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Init(), "Init() Failed");

            // Avertising Data (Flags e CompleteDeviceName)
            var adData = new AdvertisingAndScanResponseData();

            adData.addAdvertisingDataStructure(new FlagsAdvertisingDataStructure(FlagsAdvertisingDataStructure.FlagsEnum.LEGeneralDiscoverableMode | FlagsAdvertisingDataStructure.FlagsEnum.BR_EDR_NotSupported));
            adData.addAdvertisingDataStructure(new LocalNameAdvertisingDataStructure(AdvertisingDataStructureTypeEnum.CompleteLocalName, "StaceppaBLEDevice"));

            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, adData));

            Assert.IsTrue(controller.MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny));

            controller.Stop();
        }

        [Test]
        public void TestAddService()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort startHandle;
            ushort endHandle;

            Assert.IsTrue(controller.AddService(HCICommand_GATT_Add_Service.UUIDEnum.PRIMARY_SERVICE, 3, out startHandle, out endHandle));

            controller.Stop();
        }

        [Test]
        public void TestAddAttribute()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Reset(HCIController.ResetTypeEnum.HardReset), "Reset() Failed");

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort startHandle;
            ushort endHandle;

            Assert.IsTrue(controller.AddService(HCICommand_GATT_Add_Service.UUIDEnum.PRIMARY_SERVICE, 3, out startHandle, out endHandle));


            Assert.IsTrue(controller.AddAttribute(0x2803, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ, out startHandle, out endHandle));

            Assert.IsTrue(controller.AddAttribute(0x2A00, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ, out startHandle, out endHandle));

            Assert.AreEqual(0x0001, startHandle);
            Assert.AreEqual(0x0003, endHandle);

            controller.Stop();
        }

        [Test]
        public void TestRespondToServiceDiscoveryRequest()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Reset(HCIController.ResetTypeEnum.HardReset), "Reset() Failed");

            Assert.IsTrue(controller.Init(), "Init() Failed");

            // Aggiorna gli advertising Data (Flags e CompleteDeviceName)
            var adData = new AdvertisingAndScanResponseData();

            adData.addAdvertisingDataStructure(new FlagsAdvertisingDataStructure(FlagsAdvertisingDataStructure.FlagsEnum.LEGeneralDiscoverableMode | FlagsAdvertisingDataStructure.FlagsEnum.BR_EDR_NotSupported));
            adData.addAdvertisingDataStructure(new LocalNameAdvertisingDataStructure(AdvertisingDataStructureTypeEnum.CompleteLocalName, "StaceppaBLEDevice"));

            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, adData));

            // Aggiunge un servizio...
            ushort startHandle;
            ushort endHandle;

            Assert.IsTrue(controller.AddService(HCICommand_GATT_Add_Service.UUIDEnum.PRIMARY_SERVICE, 3, out startHandle, out endHandle));

            // ...con un attributo

            Assert.IsTrue(controller.AddAttribute(0x2803, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ, out startHandle, out endHandle));

            Assert.IsTrue(controller.AddAttribute(0x2A00, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ | HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_WRITE,
                out startHandle, out endHandle));

            Assert.AreEqual(0x0001, startHandle);
            Assert.AreEqual(0x0003, endHandle);

            // Rende discoverabile
            Assert.IsTrue(controller.MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny));

            AutoResetEvent are = new AutoResetEvent(false);

            ushort connHandle = 0xffff;

            // Attende la richiesta di discovery dei servizi
            controller.ServiceDiscoveryRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Service discovery request received from connHandle {0:X}, start attribute:{1:X} end attribute:{2:X}", evt.ConnHandle, evt.StartHandle, evt.EndHandle);
                connHandle = evt.ConnHandle;

                Assert.IsTrue(controller.RespondToServiceDiscoveryRequest(connHandle, new HCICommand_GATT_Read_By_Group_Type_Response.AttributeGroupData[]
                        { 
                            new HCICommand_GATT_Read_By_Group_Type_Response.AttributeGroupData(0x0001,0xFFFF,0x1800)
                        }));

                //are.Set();
            });

            //Assert.IsTrue(are.WaitOne());



            // Attende la richiesta di discovery degli attributi del servizio
            controller.ReadServiceAttributesByTypeRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Read service attributes request received from connHandle {0:X}, start attribute:{1:X}, end attribute:{2:X}, type:{3:X}",
                    evt.ConnHandle, evt.StartHandle, evt.EndHandle,
                    evt.Type, evt.Type16 != null ? evt.Type16.ToHexString() : string.Format("{0:X}", evt.Type));

                connHandle = evt.ConnHandle;

                switch (evt.Type)
                {
                    case 0x2803:
                        Assert.IsTrue(controller.RespondToReadServiceAttributesByTypeRequest(connHandle, new HCICommand_GATT_Read_By_Type_Response.AttributeData[]
                            { 
                                new  HCICommand_GATT_Read_By_Type_Response.AttributeData(0x0002,
                                    HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Read | HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Write,
                                    0x0003,0x2A00)
                            }));
                        break;
                }

                //are.Set();
            });

            //are.WaitOne();

            // Accetta e processa la richiesta di lettura della caratteristica 0x0003
            controller.ReadCharacteristicValueRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Read characteristic value request received from connHandle {0:X}, attribute handle:{1:X}",
                    evt.ConnHandle, evt.AttributeHandle);

                connHandle = evt.ConnHandle;

                switch (evt.AttributeHandle)
                {
                    case 0x0002:
                        Assert.IsTrue(controller.RespondToReadCharacteristicValueRequest(connHandle, new byte[] { 0x0A, 0x03, 0x00, 0x00, 0x2A }));
                        break;

                    case 0x0003:
                        Assert.IsTrue(controller.RespondToReadCharacteristicValueRequest(connHandle, string.Format("StaceppaValue {0}", DateTime.Now.ToLongTimeString())));
                        break;
                }

            });

            // Attende la richiesta di scrittura della caratteristica 0x0002
            controller.WriteCharacteristicValueRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Write characteristic value request received from connHandle {0:X}, attribute handle:{1:X}, value (as string):{2}",
                    evt.ConnHandle, evt.AttributeHandle, evt.ValueAsString);

                connHandle = evt.ConnHandle;

                switch (evt.AttributeHandle)
                {
                    case 0x0003:
                        Assert.IsTrue(controller.RespondToWriteCharacteristicValueRequest(connHandle));
                        break;
                }

                if (evt.ValueAsString.ToUpperInvariant() == "EXIT") are.Set();
            });

            // Attende la chiusura del link
            controller.LinkTerminated += ((evt) =>
            {
                Trace.TraceInformation("Link Terminated from connHandle {0:X}, reason:{1}", evt.ConnHandle, evt.Reason.ToString());

                connHandle = evt.ConnHandle;

                are.Set();
            });

            are.WaitOne();

            Thread.Sleep(1000); // Se chiudo la connessione troppo presto BLE Device Monitor crasha!

            controller.Stop();
        }

        [Test]
        public void TestRespondToCharacteristicReadOrWriteRequests()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Reset(HCIController.ResetTypeEnum.HardReset), "Reset() Failed");

            Assert.IsTrue(controller.Init(), "Init() Failed");

            // Aggiorna gli advertising Data (Flags e CompleteDeviceName)
            var adData = new AdvertisingAndScanResponseData();

            adData.addAdvertisingDataStructure(new FlagsAdvertisingDataStructure(FlagsAdvertisingDataStructure.FlagsEnum.LEGeneralDiscoverableMode | FlagsAdvertisingDataStructure.FlagsEnum.BR_EDR_NotSupported));
            adData.addAdvertisingDataStructure(new LocalNameAdvertisingDataStructure(AdvertisingDataStructureTypeEnum.CompleteLocalName, "StaceppaBLEDevice"));

            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, adData));

            // Aggiunge un servizio...
            ushort startHandle;
            ushort endHandle;

            Assert.IsTrue(controller.AddService(HCICommand_GATT_Add_Service.UUIDEnum.PRIMARY_SERVICE, 3, out startHandle, out endHandle));

            // ...con un attributo
            Assert.IsTrue(controller.AddAttribute(0x2803, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ, out startHandle, out endHandle));

            Assert.IsTrue(controller.AddAttribute(0x2A00, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ | HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_WRITE,
                out startHandle, out endHandle));

            Assert.AreEqual(0x0001, startHandle);
            Assert.AreEqual(0x0003, endHandle);

            // Rende discoverabile
            Assert.IsTrue(controller.MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny));

            AutoResetEvent are = new AutoResetEvent(false);

            ushort connHandle = 0xffff;

            // Accetta e processa la richiesta di lettura della caratteristica 0x0002
            controller.ReadCharacteristicValueRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Read characteristic value request received from connHandle {0:X}, attribute handle:{1:X}",
                    evt.ConnHandle, evt.AttributeHandle);

                connHandle = evt.ConnHandle;

                switch (evt.AttributeHandle)
                {
                    case 0x0003:
                        Assert.IsTrue(controller.RespondToReadCharacteristicValueRequest(connHandle, string.Format("StaceppaValue {0}", DateTime.Now.ToLongTimeString())));
                        break;
                }

            });

            // Attende la richiesta di scrittura della caratteristica 0x0002
            controller.WriteCharacteristicValueRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Write characteristic value request received from connHandle {0:X}, attribute handle:{1:X}, value (as string):{2}",
                    evt.ConnHandle, evt.AttributeHandle, evt.ValueAsString);

                connHandle = evt.ConnHandle;

                switch (evt.AttributeHandle)
                {
                    case 0x0003:
                        Assert.IsTrue(controller.RespondToWriteCharacteristicValueRequest(connHandle));
                        break;
                }

                are.Set();
            });

            are.WaitOne();

            controller.Stop();
        }

        [Test]
        public void TestResetSystem()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Reset(HCIController.ResetTypeEnum.HardReset), "Reset() Failed");

            controller.Stop();
        }

        [Test]
        public void TestBLEDeviceMonitorAndroid()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Reset(HCIController.ResetTypeEnum.HardReset), "Reset() Failed");

            Assert.IsTrue(controller.Init(), "Init() Failed");

            // Aggiorna gli advertising Data (Flags e CompleteDeviceName)
            var adData = new AdvertisingAndScanResponseData();

            adData.addAdvertisingDataStructure(new FlagsAdvertisingDataStructure(FlagsAdvertisingDataStructure.FlagsEnum.LEGeneralDiscoverableMode | FlagsAdvertisingDataStructure.FlagsEnum.BR_EDR_NotSupported));
            adData.addAdvertisingDataStructure(new LocalNameAdvertisingDataStructure(AdvertisingDataStructureTypeEnum.CompleteLocalName, "StaceppaBLEDevice"));

            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, adData));

            // Aggiunge un servizio...
            ushort startHandle;
            ushort endHandle;

            Assert.IsTrue(controller.AddService(HCICommand_GATT_Add_Service.UUIDEnum.PRIMARY_SERVICE, 3, out startHandle, out endHandle));

            // ...con un attributo
            Assert.IsTrue(controller.AddAttribute(0x2803, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ, out startHandle, out endHandle));

            Assert.IsTrue(controller.AddAttribute(0x2A00, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ | HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_WRITE, out startHandle, out endHandle));

            Assert.AreEqual(0x0001, startHandle);
            Assert.AreEqual(0x0003, endHandle);

            // Rende discoverabile
            Assert.IsTrue(controller.MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny));

            AutoResetEvent are = new AutoResetEvent(false);

            ushort connHandle = 0xffff;
            int counter = 0;

            // Accetta e processa la richiesta di lettura della caratteristica 0x0002
            controller.ReadCharacteristicValueRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Read characteristic value request received from connHandle {0:X}, attribute handle:{1:X}",
                    evt.ConnHandle, evt.AttributeHandle);

                connHandle = evt.ConnHandle;

                switch (evt.AttributeHandle)
                {
                    case 0x0003:
                        Assert.IsTrue(controller.RespondToReadCharacteristicValueRequest(connHandle, string.Format("Valore {0}", (++counter).ToString())));
                        break;
                }

            });

            // Attende la richiesta di scrittura della caratteristica 0x0003
            controller.WriteCharacteristicValueRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Write characteristic value request received from connHandle {0:X}, attribute handle:{1:X}, value (as string):{2}",
                    evt.ConnHandle, evt.AttributeHandle, evt.ValueAsString);

                connHandle = evt.ConnHandle;

                switch (evt.AttributeHandle)
                {
                    case 0x0003:
                        Assert.IsTrue(controller.RespondToWriteCharacteristicValueRequest(connHandle));
                        break;
                }

                if (evt.ValueAsString.ToUpperInvariant() == "EXIT") are.Set();
            });

            // Attende la chiusura del link
            controller.LinkTerminated += ((evt) =>
            {
                Trace.TraceInformation("Link Terminated from connHandle {0:X}, reason:{1}", evt.ConnHandle, evt.Reason.ToString());

                connHandle = evt.ConnHandle;

                are.Set();
            });

            are.WaitOne();

            Thread.Sleep(1000); // Se chiudo la connessione troppo presto BLE Device Monitor crasha!

            controller.Stop();
        }


        [Test]
        public void TestAndroidBLEExplorer()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            AutoResetEvent are = new AutoResetEvent(false);

            Assert.IsTrue(controller.Reset(HCIController.ResetTypeEnum.HardReset), "Reset() Failed");

            Assert.IsTrue(controller.Init(), "Init() Failed");

            // Aggiorna gli advertising Data (Flags e CompleteDeviceName)
            var adData = new AdvertisingAndScanResponseData();

            adData.addAdvertisingDataStructure(new FlagsAdvertisingDataStructure(FlagsAdvertisingDataStructure.FlagsEnum.LEGeneralDiscoverableMode | FlagsAdvertisingDataStructure.FlagsEnum.BR_EDR_NotSupported));
            adData.addAdvertisingDataStructure(new LocalNameAdvertisingDataStructure(AdvertisingDataStructureTypeEnum.CompleteLocalName, "StaceppaBLEDevice"));

            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, adData));

            // Aggiunge un servizio...
            ushort startHandle;
            ushort endHandle;

            Assert.IsTrue(controller.AddService(HCICommand_GATT_Add_Service.UUIDEnum.PRIMARY_SERVICE, 7, out startHandle, out endHandle));

            // ...con 4 attributi (2 caratteristiche)
            Assert.IsTrue(controller.AddAttribute(0x2803, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ, out startHandle, out endHandle));

            Assert.IsTrue(controller.AddAttribute(0x2A00, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ | HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_WRITE,
                out startHandle, out endHandle));

            Assert.IsTrue(controller.AddAttribute(0x2803, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ, out startHandle, out endHandle));

            Assert.IsTrue(controller.AddAttribute(0xFF01, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ | HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_WRITE,
                out startHandle, out endHandle));

            Assert.IsTrue(controller.AddAttribute(0x2902, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ | HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_WRITE,
                out startHandle, out endHandle));

            Assert.IsTrue(controller.AddAttribute(0x2901, HCICommand_GATT_Add_Attribute.PermissionsEnum.GATT_PERMIT_READ,
                out startHandle, out endHandle));

            Assert.AreEqual(0x0001, startHandle);
            Assert.AreEqual(0x0007, endHandle);

            // Notifica la fine del periodo discoverabile
            controller.DiscoverableEnd += ((evt) =>
            {
                Trace.TraceInformation("Discoverable Ended!");

                are.Set();
            });

            // Rende discoverabile
            Assert.IsTrue(controller.MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny));

            ushort connHandle = 0xffff;

            // Risponde alla richiesta di discovery dei servizi
            controller.ServiceDiscoveryRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Service discovery request received from connHandle {0:X}, start attribute:{1:X} end attribute:{2:X}", evt.ConnHandle, evt.StartHandle, evt.EndHandle);
                connHandle = evt.ConnHandle;

                Assert.IsTrue(controller.RespondToServiceDiscoveryRequest(connHandle, new HCICommand_GATT_Read_By_Group_Type_Response.AttributeGroupData[]
                        { 
                            new HCICommand_GATT_Read_By_Group_Type_Response.AttributeGroupData(0x0001,0xFFFF,0x1800)
                        }));

            });


            // Risponde alla richiesta di discovery degli attributi del servizio
            controller.ReadServiceAttributesByTypeRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Read service attributes request received from connHandle {0:X}, start attribute:{1:X}, end attribute:{2:X}, type:{3:X}, type16:{4}",
                    evt.ConnHandle, evt.StartHandle, evt.EndHandle,
                    evt.Type, evt.Type16 != null ? evt.Type16.ToHexString() : "NULL");

                connHandle = evt.ConnHandle;

                switch (evt.Type)
                {
                    case 0x2803:
                        Assert.IsTrue(controller.RespondToReadServiceAttributesByTypeRequest(connHandle, new HCICommand_GATT_Read_By_Type_Response.AttributeData[]
                            { 
                                new  HCICommand_GATT_Read_By_Type_Response.AttributeData(0x0002,
                                    HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Read | 
                                    HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Write,
                                    0x0003,0x2A00),
                                new  HCICommand_GATT_Read_By_Type_Response.AttributeData(0x0004,
                                    HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Read | 
                                    HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Write |
                                    HCICommand_GATT_Read_By_Type_Response.AttributeData.CharacteristicDeclarationPropertiesEnum.Notify,
                                    0x0005,0xFF01)
                            }));
                        break;
                }

            });

            int counter = 0;
            byte value = 0;
            bool notificationEnabled = false;

            // Accetta e processa la richiesta di lettura della caratteristica 0x0003
            controller.ReadCharacteristicValueRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Read characteristic value request received from connHandle {0:X}, attribute handle:{1:X}",
                    evt.ConnHandle, evt.AttributeHandle);

                connHandle = evt.ConnHandle;

                switch (evt.AttributeHandle)
                {
                    case 0x0003:
                        Assert.IsTrue(controller.RespondToReadCharacteristicValueRequest(connHandle, string.Format("Val_{0}\0", (++counter).ToString())));
                        break;

                    case 0x0005:
                        Assert.IsTrue(controller.RespondToReadCharacteristicValueRequest(connHandle, value));
                        break;

                    case 0x0006:
                        Assert.IsTrue(controller.RespondToReadCharacteristicValueRequest(connHandle, notificationEnabled));
                        break;

                    case 0x0007:
                        Assert.IsTrue(controller.RespondToReadCharacteristicValueRequest(connHandle, "Caratteristica Custom"));
                        break;
                }

            });

            Timer tmr = new Timer((state) =>
            {
                if (!notificationEnabled) return;

                value++;

                Assert.IsTrue(controller.RaiseNotification(connHandle, 0x0005, value));
            }
            , null, 0, 1000);

            // Risponde alla richiesta di scrittura della caratteristica 0x0002
            controller.WriteCharacteristicValueRequestReceived += ((evt) =>
            {
                Trace.TraceInformation("Write characteristic value request received from connHandle {0:X}, attribute handle:{1:X}, value (as string):{2}, value (as byte array):{3}",
                    evt.ConnHandle, evt.AttributeHandle, evt.ValueAsString, evt.Value.ToHexString());

                connHandle = evt.ConnHandle;

                switch (evt.AttributeHandle)
                {
                    case 0x0003:
                        Assert.IsTrue(controller.RespondToWriteCharacteristicValueRequest(connHandle));
                        break;

                    case 0x0005:
                        value = evt.Value[0];
                        Assert.IsTrue(controller.RespondToWriteCharacteristicValueRequest(connHandle));
                        break;

                    case 0x0006:
                        notificationEnabled = evt.ValueAsBoolean;
                        Assert.IsTrue(controller.RespondToWriteCharacteristicValueRequest(connHandle));
                        break;
                }

            });

            // Attende la chiusura del link
            controller.LinkTerminated += ((evt) =>
            {
                Trace.TraceInformation("Link Terminated from connHandle {0:X}, reason:{1}", evt.ConnHandle, evt.Reason.ToString());

                notificationEnabled = false;

                connHandle = evt.ConnHandle;

                are.Set();
            });

            are.WaitOne();

            Thread.Sleep(1000); // Se chiudo la connessione troppo presto BLE Device Monitor crasha!

            controller.Stop();
        }

        [Test]
        public void TestEndDiscoverable()
        {
            HCISimpleSerialPeripheralProfileController controller = new HCISimpleSerialPeripheralProfileController("COM4", 115200);

            controller.Start();

            AutoResetEvent are = new AutoResetEvent(false);

            Assert.IsTrue(controller.Reset(HCIController.ResetTypeEnum.HardReset), "Reset() Failed");

            Assert.IsTrue(controller.Init(), "Init() Failed");

            // Aggiorna gli advertising Data (Flags e CompleteDeviceName)
            var adData = new AdvertisingAndScanResponseData();

            adData.addAdvertisingDataStructure(new FlagsAdvertisingDataStructure(FlagsAdvertisingDataStructure.FlagsEnum.LEGeneralDiscoverableMode | FlagsAdvertisingDataStructure.FlagsEnum.BR_EDR_NotSupported));
            adData.addAdvertisingDataStructure(new LocalNameAdvertisingDataStructure(AdvertisingDataStructureTypeEnum.CompleteLocalName, "StaceppaBLEDevice"));

            Assert.IsTrue(controller.UpdateAdvertisingData(HCICommand_GAP_Update_Advertising_Data.AdvertisingTypeEnum.AdvertisementData, adData));

            // Notifica la fine del periodo discoverabile
            controller.DiscoverableEnd += ((evt) =>
            {
                Trace.TraceInformation("Discoverable Ended");

                are.Set();
            });

            // Rende discoverabile
            Assert.IsTrue(controller.MakeDiscoverable(HCICommand_GAP_Make_Discoverable.EventTypeEnum.ConnectableUndirectedAdvertisement,
                    HCICommand_GAP_Make_Discoverable.InitiatorAddrTypeEnum.ADDRTYPE_PUBLIC,
                    new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
                    HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel37 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel38 | HCICommand_GAP_Make_Discoverable.ChannelMapEnum.Channel39,
                    HCICommand_GAP_Make_Discoverable.FilterPolicyEnum.AllowScanFromAnyAllowConnectRequestFromAny));

            Thread.Sleep(5000);

            Assert.IsTrue(controller.EndDiscoverable());

            are.WaitOne();

            controller.Stop();
        }

        [Test]
        public void TestExchangeMTUSizeConversation()
        {
            GATTServerController controller = new GATTServerController("COM4", 115200);

            controller.Start("Tester BLE Peripheral");

            AutoResetEvent are = new AutoResetEvent(false);

            controller.OnLinkStateChanged += ((s, e) =>
                {
                    if (e.State)
                    {
                        Trace.TraceInformation("Link ESTABLISHED");
                    }
                    else
                    {
                        Trace.TraceInformation("Link TERMINATED");

                        are.Set();
                    }
                });

            Assert.IsTrue(controller.MakeDiscoverable());

            are.WaitOne();
        }

        [Test]
        public void TestGATTServerSimple()
        {
            GATTServerController controller = new GATTServerController("COM4", 115200);

            Assert.IsTrue(controller.Start("TestGATTServer"));

            var service1 = controller.AddService(0xAA01);

            double double_1 = Math.PI;
            string string_1 = "test";
            int int32_1 = 1971;

            service1.AddCharacteristic("Int32_1", Controllers.GATT_Entities.GATT_Characteristic.PermissionsEnum.Read | Controllers.GATT_Entities.GATT_Characteristic.PermissionsEnum.Write, Controllers.GATT_Entities.GATT_Characteristic.ValueTypeEnum.Int32);
            service1.AddCharacteristic("Double_1", Controllers.GATT_Entities.GATT_Characteristic.PermissionsEnum.Read | Controllers.GATT_Entities.GATT_Characteristic.PermissionsEnum.Write, Controllers.GATT_Entities.GATT_Characteristic.ValueTypeEnum.Double);
            service1.AddCharacteristic("String_1", Controllers.GATT_Entities.GATT_Characteristic.PermissionsEnum.Read | Controllers.GATT_Entities.GATT_Characteristic.PermissionsEnum.Write, Controllers.GATT_Entities.GATT_Characteristic.ValueTypeEnum.UTF8String);

            Assert.IsTrue(controller.RegisterServices());

            AutoResetEvent are = new AutoResetEvent(false);

            controller.OnLinkStateChanged += ((s, e) =>
                {
                    Trace.TraceInformation("Link State : {0}", e.State);

                    if (!e.State) are.Set();
                });

            controller.OnReadCharacteristicValue += ((s, e) =>
                {
                    Trace.TraceInformation("Read Characteristic : {0}", e.Characteristic.Identifier);

                    switch (e.Characteristic.Identifier)
                    {
                        case "Int32_1":
                            e.SetValue(int32_1);
                            break;

                        case "Double_1":
                            e.SetValue(double_1);
                            break;

                        case "String_1":
                            e.SetValue(string_1);
                            break;
                    }


                });

            controller.OnWriteCharacteristicValue += ((s, e) =>
                {
                    Trace.TraceInformation("Write Characteristic : {0}, Value : {1}", e.Characteristic.Identifier, e.GetValue().ToHexString());

                    switch (e.Characteristic.ValueType)
                    {
                        case GATT_Characteristic.ValueTypeEnum.Int32:
                            Trace.TraceInformation("\t(decoded value : {0})", e.GetValueAsInt32());
                            break;

                        case GATT_Characteristic.ValueTypeEnum.Double:
                            Trace.TraceInformation("\t(decoded value : {0})", e.GetValueAsDouble());
                            break;

                        case GATT_Characteristic.ValueTypeEnum.UTF8String:
                            Trace.TraceInformation("\t(decoded value : {0})", e.GetValueAsString());
                            break;
                    }

                    switch (e.Characteristic.Identifier)
                    {
                        case "Double_1":
                            double_1 = e.GetValueAsDouble();
                            break;

                        case "Int32_1":
                            double_1 = e.GetValueAsInt32();
                            break;

                        case "String_1":
                            string_1 = e.GetValueAsString();
                            break;
                    }
                });

            controller.MakeDiscoverable();

            are.WaitOne();

            controller.Stop();

            controller.Terminate();
        }

        [Test]
        public void TestGATTServerSimple_iBeacon()
        {
            GATTServerController controller = new GATTServerController("COM18", 115200);

            var msd = new ManufacturerSpecificData_iBeacon()
            {
                CompanyIdentifier = AssignedNumbers.CompanyIdentifier.Apple,
                AdvertisementIndicator = 0x1502,
                ProximityUUID = new byte[] { 0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0 },
                Major = 0x0000,
                Minor = 0x0001,
                TxCalibratedPower = -58
            };

            Assert.IsTrue(controller.Start("TestGATTServer",new ManufacturerSpecificDataAdvertisingDataStructure(msd.ToByteArray())));
            
            Assert.IsTrue(controller.AddAdvertisingDataStructures());

            AutoResetEvent are = new AutoResetEvent(false);

            controller.OnLinkStateChanged += ((s, e) =>
            {
                Trace.TraceInformation("Link State : {0}", e.State);

                if (!e.State) are.Set();
            });

            controller.MakeDiscoverable();

            are.WaitOne();

            controller.Stop();

            controller.Terminate();
        }
    }
}
